package com.example.learnmediacodec

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.media.MediaCodec
import android.media.MediaCodecList
import android.media.MediaExtractor
import android.media.MediaFormat
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore.Video
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.widget.ImageView
import android.widget.VideoView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
import java.util.concurrent.atomic.AtomicBoolean


class DecodeUsingSurfaceActivity : AppCompatActivity() {
    private val TAG = "DecodeUsingSurface"
    private var stopDecoding = false
    private var thread0: Thread? = null
    private var startTime = 0L
    private var srcWidth = 0
    private var srcHeight = 0

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var mSurface: Surface? = null
    private var videoView:VideoView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_decode_using_surface)

        paint.setColor(Color.WHITE);
        paint.setTextSize(30f);

        videoView = findViewById<VideoView>(R.id.video_view)

        videoView?.setVideoURI(Uri.parse("https://v-cdn.zjol.com.cn/276996.mp4"))
        videoView?.start()

        val surfaceView = findViewById<SurfaceView>(R.id.surface_view)

        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                Log.d("GJ", "surfaceCreated ---")
                mSurface = surfaceView.holder.surface
            }

            override fun surfaceChanged(
                holder: SurfaceHolder,
                format: Int,
                width: Int,
                height: Int
            ) {
                Log.d("GJ", "surfaceChanged ---")

                mSurface = surfaceView.holder.surface

                stopDecodingThread()

                thread0 = Thread {
                    stopDecoding = false
                    decodeToSurfaceAsync() // or decodeToSurface(holder.surface)
                }
                thread0?.start()
                startTime = System.nanoTime()
            }

            override fun surfaceDestroyed(holder: SurfaceHolder) {
                stopDecodingThread()
            }
        })

    }

    private fun stopDecodingThread() {
        stopDecoding = true
        thread0?.join()
    }

//    private fun drawWaterMark(surface: Surface) {
//        val canvas: Canvas = surface.lockCanvas(Rect(200, 200, 300, 300));
//        canvas.drawText("123", 10f, 10f, paint)
//        surface.unlockCanvasAndPost(canvas)
//    }

    private fun decodeToSurface(surface: Surface) {
        // create and configure media extractor
        val mediaExtractor = MediaExtractor()
        mediaExtractor.setDataSource(this, Uri.parse("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"), null)
//        resources.openRawResourceFd(R.raw.h264_720p).use {
//            mediaExtractor.setDataSource(it)
//        }
        val videoTrackIndex = 0
        mediaExtractor.selectTrack(videoTrackIndex)
        val videoFormat = mediaExtractor.getTrackFormat(videoTrackIndex)

        // create and configure media codec
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
        val codecName = codecList.findDecoderForFormat(videoFormat)
        val codec = MediaCodec.createByCodecName(codecName)
        // configure with surface
        codec.configure(videoFormat, surface, null, 0)

        // start decoding
        val maxInputSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
        val inputBuffer = ByteBuffer.allocate(maxInputSize)
        val bufferInfo = MediaCodec.BufferInfo()
        val timeoutUs = 10000L // 10ms
        var inputEnd = false
        var outputEnd = false

        codec.start()
        while (!outputEnd && !stopDecoding) {
            val isExtractorReadEnd =
                getInputBufferFromExtractor(mediaExtractor, inputBuffer, bufferInfo)
            if (isExtractorReadEnd) {
                inputEnd = true
            }

            // get codec input buffer and fill it with data from extractor
            // timeoutUs is -1L means wait forever
            val inputBufferId = codec.dequeueInputBuffer(-1L)
            if (inputBufferId >= 0) {
                if (inputEnd) {
                    codec.queueInputBuffer(
                        inputBufferId, 0, 0, 0,
                        MediaCodec.BUFFER_FLAG_END_OF_STREAM
                    )
                } else {
                    val codecInputBuffer = codec.getInputBuffer(inputBufferId)
                    codecInputBuffer!!.put(inputBuffer)
                    codec.queueInputBuffer(
                        inputBufferId,
                        0,
                        bufferInfo.size,
                        bufferInfo.presentationTimeUs,
                        0
                    )
                }
            }

            // get output buffer from codec and render it to image view
            // NOTE! dequeueOutputBuffer with -1L is will stuck here,  so wait 10ms here
            val outputBufferId = codec.dequeueOutputBuffer(bufferInfo, timeoutUs)
            if (outputBufferId >= 0) {
                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
                    outputEnd = true
                }
                if (bufferInfo.size > 0) {
                    val pts = bufferInfo.presentationTimeUs * 1000L + startTime
                    codec.releaseOutputBuffer(outputBufferId, pts)
                }
            }

            mediaExtractor.advance()
        }


        mediaExtractor.release()
        codec.stop()
        codec.release()
    }

    private fun decodeToSurfaceAsync() {
        // create and configure media extractor
        val mediaExtractor = MediaExtractor()

        Log.d("DecodeUsingSurface", "srcWidth:$srcWidth srcHeight:$srcHeight")

        mediaExtractor.setDataSource(this, Uri.parse("https://v-cdn.zjol.com.cn/276996.mp4"), null)

//        resources.openRawResourceFd(R.raw.h264_720p).use {
//            mediaExtractor.setDataSource(it)
//        }
        val videoTrackIndex = 0
        mediaExtractor.selectTrack(videoTrackIndex)
        val videoFormat = mediaExtractor.getTrackFormat(videoTrackIndex)

        // create and configure media codec
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
        val codecName = codecList.findDecoderForFormat(videoFormat)
        val codec = MediaCodec.createByCodecName(codecName)

        val maxInputSize = videoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
        val inputBuffer = ByteBuffer.allocate(maxInputSize)
        val bufferInfo = MediaCodec.BufferInfo()
        val inputEnd = AtomicBoolean(false)
        val outputEnd = AtomicBoolean(false)

        // set codec callback in async mode
        codec.setCallback(object : MediaCodec.Callback() {
            override fun onInputBufferAvailable(codec: MediaCodec, inputBufferId: Int) {
                val isExtractorReadEnd =
                    getInputBufferFromExtractor(mediaExtractor, inputBuffer, bufferInfo)
                if (isExtractorReadEnd) {
                    inputEnd.set(true)
                    codec.queueInputBuffer(
                        inputBufferId, 0, 0, 0,
                        MediaCodec.BUFFER_FLAG_END_OF_STREAM
                    )
                } else {
                    val codecInputBuffer = codec.getInputBuffer(inputBufferId)
                    codecInputBuffer!!.put(inputBuffer)
                    codec.queueInputBuffer(
                        inputBufferId,
                        0,
                        bufferInfo.size,
                        bufferInfo.presentationTimeUs,
                        bufferInfo.flags
                    )
                    mediaExtractor.advance()
                }
            }

            override fun onOutputBufferAvailable(
                codec: MediaCodec,
                outputBufferId: Int,
                info: MediaCodec.BufferInfo
            ) {
                if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
                    outputEnd.set(true)
                }

                // 解码帧
                if (outputBufferId > 0) {
                    var outputBuffer: ByteBuffer? = codec.getOutputBuffer(outputBufferId)

                    outputBuffer?.let {

                    }
                }

                if (info.size > 0) {
                    // render the decoded frame
                    val pts = info.presentationTimeUs * 1000L + startTime
                    codec.releaseOutputBuffer(outputBufferId, pts)
                }
            }

            override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
                e.printStackTrace()
            }

            override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
                // do nothing
            }
        })

        // configure with surface
        codec.configure(videoFormat, mSurface, null, 0)

        // start decoding
        codec.start()

        // wait for processing to complete
        while (!outputEnd.get() && !stopDecoding) {
            Thread.sleep(10)
        }

        mediaExtractor.release()
        codec.stop()
        codec.release()
    }

    @SuppressLint("WrongConstant")
    private fun getInputBufferFromExtractor(
        mediaExtractor: MediaExtractor,
        inputBuffer: ByteBuffer,
        bufferInfo: MediaCodec.BufferInfo
    ): Boolean {
        val sampleSize = mediaExtractor.readSampleData(inputBuffer, 0)
        if (sampleSize < 0) {
            return true
        }

        bufferInfo.size = sampleSize
        bufferInfo.presentationTimeUs = mediaExtractor.sampleTime
        bufferInfo.offset = 0
        bufferInfo.flags = mediaExtractor.sampleFlags

        return false
    }
}